/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "DecompressorGif.h"
#include <Gluck.h>

namespace liba
{
namespace pictureformat
{

const uint8_t GIF_IMAGE_SEPARATOR = 0x2C;
const uint8_t GIF_EXT_INTRODUCER  = 0x21;

const uint8_t GIF_PALLETE_FLAG = 0x80;

struct LogicalScreenDescriptor
{
	uint16_t	logical_screen_width;
	uint16_t	logical_screen_height;
	uint8_t		flags;
	uint8_t		background_color_index;
	uint8_t		pixel_aspect_ratio;
};

struct ImageDiscriptor
{
	uint16_t	left;
	uint16_t	top;
	uint16_t	width;
	uint16_t	height;
	uint8_t		flags;
};

#define READ_8_BIT(dst,src)	\
	*dst = *src++

#define READ_16_BIT(dst,src)	\
	*dst = *src | (*(src+1) << 8);	\
	src += sizeof(*dst)

DecompressorGif::DecompressorGif( const void * data, int size )
:	data( data ),
	decoded_data( 0 ),
	pallete_size(0),
	m_width( 0 ),
	m_height( 0 ),
	valid( false ),
	scan_line( 0 ),
	pallete( 0 ),
	out_codes( 0 ),
	prefix( 0 ),
	suffix( 0 )
{
	current_ptr = reinterpret_cast<const uint8_t*>( data );

	bool signature_check = (memcmp(current_ptr,"GIF87a",6) == 0) || (memcmp(current_ptr,"GIF89a",6) == 0);
	if (!signature_check)
		return;

	current_ptr += 6;

	LogicalScreenDescriptor descriptor;
	READ_16_BIT(&descriptor.logical_screen_width,current_ptr);
	READ_16_BIT(&descriptor.logical_screen_height,current_ptr);
	READ_8_BIT(&descriptor.flags,current_ptr);
	READ_8_BIT(&descriptor.background_color_index,current_ptr);
	READ_8_BIT(&descriptor.pixel_aspect_ratio,current_ptr);

	m_width = descriptor.logical_screen_width;
	m_height = descriptor.logical_screen_height;

	decoded_data = new ColorPacked[m_width * m_height];

	if (descriptor.flags & GIF_PALLETE_FLAG)	// Global Color Table presents
		pallete_size = 1 << ((descriptor.flags & 0x07) + 1);

	if (pallete_size)
	{
		pallete = new ColorPacked[pallete_size];
		load_pallete(pallete);
	}

	if( !find_image_descriptor() )
		return;

	ImageDiscriptor descriptor_image;
	READ_16_BIT(&descriptor_image.left,current_ptr);
	READ_16_BIT(&descriptor_image.top,current_ptr);
	READ_16_BIT(&descriptor_image.width,current_ptr);
	READ_16_BIT(&descriptor_image.height,current_ptr);
	READ_8_BIT(&descriptor_image.flags,current_ptr);

	if (descriptor_image.flags & GIF_PALLETE_FLAG)	// Local Color Table usage
	{
		pallete_size = 1 << ((descriptor_image.flags & 0x07) + 1);

		delete [] pallete; pallete = new ColorPacked[pallete_size];
		load_pallete(pallete);
	}

	code_size = *current_ptr + 1;
	// assert(code_size <= 12);

	clear_code = 1 << (code_size - 1);

	eof_code = clear_code + 1;
	bit_mask = pallete_size - 1;

	initial_code_size = code_size;
	initial_max_code = 1 << code_size;
	initial_free_code = clear_code + 2;

	max_code = initial_max_code;
	free_code = initial_free_code;

	bits_remainder = 0;
	last_ptr = current_ptr;
	move_next_byte();

	out_codes = new uint16_t[4096];
	out_codes_count = 0;

	prefix = new uint16_t[4096];
	suffix = new uint16_t[4096];

	code = read_next_code(code_size);

	ColorPacked * scanbuffer = decoded_data;

	for( ; code != eof_code; code = read_next_code(code_size) )
	{
		if (code == clear_code)
		{
			code_size = initial_code_size;
			max_code = initial_max_code;
			free_code = initial_free_code;

			previous_code = code = read_next_code(code_size);
			result = code & bit_mask;

			*scanbuffer++ = pallete[result];
		}
		else
		{
			input_code = code;

			if (code >= free_code)
			{
				code = previous_code;
				out_codes[out_codes_count++] = result;
			}

			while (code > bit_mask)
			{
				out_codes[out_codes_count++] = suffix[code];
				code = prefix[code];
			}
			out_codes[out_codes_count++] = result = code & bit_mask;

			while ((out_codes_count - 1)  >= 0)
			{
				*scanbuffer++ = pallete[out_codes[--out_codes_count]];
			}

			if (free_code < max_code)
			{
				prefix[free_code] = previous_code;
				suffix[free_code] = result;
				++free_code;

				previous_code = input_code;
			}

			if (free_code >= max_code && code_size < 12)
			{
				++code_size;
				max_code <<= 1;
			}
		}
	}

	valid = true;
}

DecompressorGif::~DecompressorGif()
{
	delete [] decoded_data; decoded_data = 0;

	delete [] pallete; pallete = 0;
	delete [] out_codes; out_codes = 0;
	delete [] prefix; prefix = 0;
	delete [] suffix; suffix = 0;

	data = 0;
}

bool DecompressorGif::find_image_descriptor()
{
	while (1)
	{
		uint8_t value = *current_ptr++;

		if (value == GIF_IMAGE_SEPARATOR)
			break;
		else
		{
			if (value == GIF_EXT_INTRODUCER)
			{
				++current_ptr;	// skip label
				while (value = *current_ptr++)	// skip block
					current_ptr += value;
			}
			else
				return false;
		}
	}

	return true;
}

void DecompressorGif::load_pallete(ColorPacked *pallete)
{
	for (uint16_t i = 0; i < pallete_size; ++i)
	{
		unsigned char red = current_ptr[0];
		unsigned char green = current_ptr[1];
		unsigned char blue = current_ptr[2];		
		unsigned char a = 255;

		if( red > 250 )
			red = 255;
		if( green > 250 )
			green = 255;
		if( blue > 250 )
			blue = 255;

		pallete[i] = ColorPacked( red , green , blue , a );

		current_ptr += 3;
	}
}


void DecompressorGif::unpack_scanline( void * dst )
{
	convert_scanline<A8R8G8B8>(desired_format, decoded_data + m_width*(m_height - scan_line - 1), dst, m_width );
	scan_line++;
}


static const uint16_t BitsMask[] =
{
	0x0000,
		0x0001, 0x0003, 0x0007, 0x000f,
		0x001f, 0x003f, 0x007f, 0x00ff,
		0x01ff, 0x03ff, 0x07ff, 0x0fff,
		0x1fff, 0x3fff, 0x7fff, 0xffff
};

uint16_t DecompressorGif::read_next_code(uint16_t bits)
{
	uint16_t mask = BitsMask[bits];
	bits += bits_remainder;

	uint16_t result = *current_ptr >> bits_remainder;

	if (bits >= 8)
	{
		move_next_byte();

		if (bits > 8)
		{
			result |= *current_ptr << (8 - bits_remainder);

			if (bits >= 16)
			{
				move_next_byte();

				if (bits > 16)
					result |= *current_ptr << (16 - bits_remainder);
			}
		}
	}

	bits_remainder = bits & 7;

	return (result & mask);
}

void DecompressorGif::move_next_byte()
{
	++current_ptr;
	if (current_ptr >= last_ptr)
	{
		uint8_t block_size = *current_ptr++;
		last_ptr = current_ptr + block_size;
	}
}

void DecompressorGif::skip_scanline(int count)
{
	scan_line += count;
}

} // namespace pictureformat
} // namespace liba

